#include "AbstractTensorProductSurfaces.h"
#include "RealSquareMatrices.h"

using namespace cagd;
using namespace std;

AbstractTensorProductSurface::AbstractTensorProductSurface(GLdouble u_min, GLdouble u_max, GLdouble v_min, GLdouble v_max, GLuint row_count, GLuint column_count, GLboolean is_closed)
{
    _u_min = u_min;
    _u_max = u_max;
    _v_min = v_min;
    _v_max = v_max;
    _is_closed = is_closed;
    _control_net.ResizeRows(row_count);
    _control_net.ResizeColumns(column_count);
}

AbstractTensorProductSurface::AbstractTensorProductSurface(const AbstractTensorProductSurface &surface)
{
    _u_min = surface._u_min;
    _u_max = surface._u_max;
    _v_min = surface._v_min;
    _v_max = surface._v_max;
    _is_closed = surface._is_closed;
    _control_net = surface._control_net;
}

AbstractTensorProductSurface& AbstractTensorProductSurface::operator =(const AbstractTensorProductSurface &surface)
{
    _u_min = surface._u_min;
    _u_max = surface._u_max;
    _v_min = surface._v_min;
    _v_max = surface._v_max;
    _is_closed = surface._is_closed;
    _control_net = surface._control_net;

    return *this;
}

GLvoid AbstractTensorProductSurface::SetUInterval(GLdouble u_min, GLdouble u_max)
{
    _u_min = u_min;
    _u_max = u_max;
}

GLvoid AbstractTensorProductSurface::SetVInterval(GLdouble v_min, GLdouble v_max)
{
    _v_min = v_min;
    _v_max = v_max;
}

GLvoid AbstractTensorProductSurface::GetUInterval(GLdouble &u_min, GLdouble &u_max) const
{
    u_max = _u_max;
    u_min = _u_min;
}

GLvoid AbstractTensorProductSurface::GetVInterval(GLdouble &v_min, GLdouble &v_max) const
{
    v_max = _v_max;
    v_min = _v_min;
}

GLboolean AbstractTensorProductSurface::SetControlPoint(GLuint row, GLuint column, const DCoordinate &point)
{
    if (row >= _control_net.GetRowCount() || column >= _control_net.GetColumnCount())
        return false;

    _control_net.SetElement(row, column, point);
    return true;
}

GLboolean AbstractTensorProductSurface::SetControlPoint(GLuint row, GLuint column, GLdouble x, GLdouble y, GLdouble z)
{
    if (row >= _control_net.GetRowCount() || column >= _control_net.GetColumnCount())
        return false;

    DCoordinate point(x, y, z);
    _control_net.SetElement(row, column, point);
    return true;
}

GLboolean AbstractTensorProductSurface::GetControlPoint(GLuint row, GLuint column, DCoordinate &point) const
{
    if (row >= _control_net.GetRowCount() || column >= _control_net.GetColumnCount())
        return false;

    point = _control_net(row, column);
    return true;
}

GLboolean AbstractTensorProductSurface::GetControlPoint(GLuint row, GLuint column, GLdouble &x, GLdouble &y, GLdouble &z) const
{
    if (row >= _control_net.GetRowCount() || column >= _control_net.GetColumnCount())
        return false;

    DCoordinate point;
    point = _control_net(row, column);
    x = point[0];
    y = point[1];
    z = point[2];
    return true;
}


GLboolean AbstractTensorProductSurface::GenerateMesh(GLuint u_div_point_count, GLuint v_div_point_count, TriangulatedMesh &mesh) const
{
    if (u_div_point_count <= 1 || v_div_point_count <= 1)
        return false;

    GLuint vertex_count = u_div_point_count * v_div_point_count;

    mesh._vertex.resize(vertex_count);
    mesh._normal.resize(vertex_count);
    mesh._tex.resize(vertex_count);

    GLuint face_count = 2 * (u_div_point_count - 1) * (v_div_point_count - 1);

    mesh._face.resize(face_count);

    GLdouble du = (_u_max - _u_min) / (u_div_point_count - 1);
    GLdouble dv = (_v_max - _v_min) / (v_div_point_count - 1);

    GLfloat sdu = 1.0f / (u_div_point_count - 1);
    GLfloat tdv = 1.0f / (v_div_point_count - 1);

    GLuint current_face = 0;

    SurfaceInformation pd;

    for (GLuint i = 0; i < u_div_point_count; i++)
    {
        GLdouble u = _u_min + i * du;
        GLfloat  s = i * sdu;
        for (GLuint j = 0; j < v_div_point_count; j++)
        {
            GLdouble v = _v_min + j * dv;
            GLfloat t = j * tdv;
            GLuint index[4];

            index[0] = i * v_div_point_count + j;
            index[1] = index[0] + 1;
            index[2] = index[1] + v_div_point_count;
            index[3] = index[2] - 1;

            CalculateAllInformation(u, v, pd);

            mesh._vertex[index[0]] = pd.point;

            mesh._normal[index[0]] = pd.diff1u;
            mesh._normal[index[0]] %= pd.diff1v;
            mesh._normal[index[0]].Normalize();

            mesh._tex[index[0]].s = s;
            mesh._tex[index[0]].t = t;

            if (i < u_div_point_count - 1 && j < v_div_point_count - 1)
            {
                mesh._face[current_face][0] = index[0];
                mesh._face[current_face][1] = index[1];
                mesh._face[current_face][2] = index[2];
                current_face++;

                mesh._face[current_face][0] = index[0];
                mesh._face[current_face][1] = index[2];
                mesh._face[current_face][2] = index[3];
                current_face++;
            }
        }
    }
    return true;
}

GLboolean AbstractTensorProductSurface::UpdateControlPointsForInterpolation(
        const RowMatrix<GLdouble> &u_knot_vector,
        const ColumnMatrix<GLdouble> &v_knot_vector,
        Matrix<DCoordinate> &data_points)
{
    GLuint row_count = _control_net.GetRowCount();
    if (!row_count)
        return false;

    GLuint column_count = _control_net.GetColumnCount();
    if (!column_count)
        return false;

    if (u_knot_vector.GetColumnCount() != row_count || v_knot_vector.GetRowCount() != column_count ||
        data_points.GetRowCount() != row_count || data_points.GetColumnCount() != column_count)
        return false;

    RowMatrix<GLdouble> u_blending_values;

    RealSquareMatrix u_collocation_matrix(row_count);

    for (GLuint i = 0; i < row_count; i++)
    {
        if (!UBlendingFunctionValues(u_knot_vector(i), u_blending_values))
            return false;
        u_collocation_matrix.SetRow(i, u_blending_values);
    }

    if (!u_collocation_matrix.PerformLUDecomposition())
        return false;

    RowMatrix<GLdouble> v_blending_values;

    RealSquareMatrix v_collocation_matrix(column_count);

    for (GLuint j = 0; j < column_count; j++)
    {
        if (!VBlendingFunctionValues(v_knot_vector(j), v_blending_values))
            return false;
        v_collocation_matrix.SetRow(j, v_blending_values);
    }

    if (!v_collocation_matrix.PerformLUDecomposition())
        return false;

    Matrix<DCoordinate> a(row_count, column_count);

    if (!u_collocation_matrix.SolveLinearSystem(data_points, a))
        return false;

    if (!v_collocation_matrix.SolveLinearSystem(a, _control_net, false))
        return false;

    return true;
}

GLvoid AbstractTensorProductSurface::DeleteVBOOfControlNet()
{
    if (!_vbo_control_net)
        return;
    glDeleteBuffersARB(1, &_vbo_control_net);
    _vbo_control_net = 0;
}

GLboolean AbstractTensorProductSurface::RenderControlNet(GLenum render_mode) const
{
    if (!_vbo_control_net)
        return false;

    if (render_mode != GL_LINE_STRIP && render_mode != GL_LINE_LOOP && render_mode != GL_POINTS)
        return false;

    GLuint point_count = _control_net.GetColumnCount() * _control_net.GetRowCount() * 2;

    glEnableClientState(GL_VERTEX_ARRAY);

    glBindBufferARB(GL_ARRAY_BUFFER, _vbo_control_net);
    glVertexPointer(3, GL_FLOAT, 0, (const GLvoid *)0);
    glDrawArrays(render_mode, 0, point_count);

    glDisableClientState(GL_VERTEX_ARRAY);
    glBindBufferARB(GL_ARRAY_BUFFER, 0);

    return true;
}

GLboolean AbstractTensorProductSurface::UpdateVBOOfControlNet(GLenum usage_type)
{
    GLuint row = _control_net.GetRowCount();
    GLuint col = _control_net.GetColumnCount();
    GLuint control_point_count = row * col;

    if (!control_point_count)
        return false;

    if (usage_type != GL_STREAM_DRAW  && usage_type != GL_STREAM_READ  && usage_type != GL_STREAM_COPY &&
        usage_type != GL_STATIC_DRAW  && usage_type != GL_STATIC_READ  && usage_type != GL_STATIC_COPY &&
        usage_type != GL_DYNAMIC_DRAW && usage_type != GL_DYNAMIC_READ && usage_type != GL_DYNAMIC_COPY)
        return false;

    DeleteVBOOfControlNet();

    glGenBuffersARB(1, &_vbo_control_net);
    if (!_vbo_control_net)
        return false;

    glBindBufferARB(GL_ARRAY_BUFFER, _vbo_control_net);
    glBufferDataARB(GL_ARRAY_BUFFER, control_point_count * 6 * sizeof(GLfloat), 0, usage_type);

    GLfloat* coordinate = (GLfloat*)glMapBufferARB(GL_ARRAY_BUFFER, GL_WRITE_ONLY);
    GLboolean asc = true;
    for (GLuint r = 0; r < row; r++)
    {
        if (asc)
        {
            for (GLuint c = 0; c < col; c++)
            {
                for (GLuint i = 0; i < 3; ++i)
                {
                    *coordinate = (GLfloat)_control_net(r, c)[i];
                    ++coordinate;
                }
            }
            asc = false;
        }
        else
        {
            for (GLuint c = col; c > 0; c--)
            {
                for (GLuint i = 0; i < 3; ++i)
                {
                    *coordinate = (GLfloat)_control_net(r, c - 1)[i];
                    ++coordinate;
                }
            }
            asc = true;
        }
    }
    asc = true;
    for (GLuint c = 0; c < col; c++)
    {
        if (asc)
        {
            for (GLuint r = 0; r < row; r++)
            {
                for (GLuint i = 0; i < 3; ++i)
                {
                    *coordinate = (GLfloat)_control_net(r, c)[i];
                    ++coordinate;
                }
            }
            asc = false;
        }
        else
        {
            for (GLuint r = row; r > 0; r--)
            {
                for (GLuint i = 0; i < 3; ++i)
                {
                    *coordinate = (GLfloat)_control_net(r - 1, c)[i];
                    ++coordinate;
                }
            }
            asc = true;
        }
    }
    if (!glUnmapBufferARB(GL_ARRAY_BUFFER))
        return false;

    return true;
}

AbstractTensorProductSurface::~AbstractTensorProductSurface()
{
    DeleteVBOOfControlNet();
}
